#include "config_info.h"
#include "szarp_config.h"

#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>

void TTagHolder::InsertTag(const std::string& tag) {
	tags.insert(tag);
}

void TTagHolder::EraseTag(const std::string& tag) {
	tags.erase(tag);
}

bool TTagHolder::ValidateTag(const std::string& tag) const
{
	if(tag.empty()) return false;

	const std::set<std::string> forbidden_tags = { "all", "none"};
	if(forbidden_tags.count(tag) > 0)
		return false;

	const std::set<char> forbidden_chars = {',', '+', '-', ' '};
	for(const auto& c : forbidden_chars)
	{
		if(tag.find(c) != std::string::npos)
			return false;
	}
	return true;
}

void TTagHolder::ParseTags(const std::string& tags_string)
{
	if(tags_string == "") return;

	std::vector<std::string> tags;
	boost::split(tags, tags_string, [](char c){ return c == ',';});
	for(auto& tag : tags)
	{
		boost::trim(tag);
		auto actual_tag = tag;

		// every tag should start with "+/-" so we extract actual tag
		actual_tag.erase(0, 1);

		if(ValidateTag(actual_tag) == false)
			throw InvalidTagException(boost::str(boost::format("Invalid tag %1%") % actual_tag));

		if(boost::starts_with(tag, "+")) {
			InsertTag(actual_tag);
		}
		else if(boost::starts_with(tag, "-")) {
			EraseTag(actual_tag);
		}
		else {
			throw InvalidTagException(boost::str(boost::format("Forbidden starting char %1%.\
				Should start with either + or -.") % tag.front()));
		}
	}
}

bool TagManager::HasValidTags(const TTagHolder& tag_holder) const
{
	if(IsEveryParamValid()) return true;

	if(tag_holder.TagEmpty())
		return IsParamWithoutTagsValid();

	for(const auto& tag : valid_tags)
	{
		if(tag_holder.HasTag(tag))
			return true;
	}

	return false;
}

void TagManager::AddToCurrentPrefix(const TagSet& tag_set)
{
	for(const auto& tag : tag_set)
	{
		prefix_tags[current_prefix].insert(tag);
	}
}

std::set<std::string> TagManager::GetGlobalValidTags()
{
	return valid_tags;
}

void TagManager::SetCurrentPrefix(const std::wstring& prefix)
{
	current_prefix = prefix;
}

void TagManager::AddNoneToPrefix()
{
	prefix_tags[current_prefix].insert(NONE_TAG);
}

void TagManager::SetEveryParamValid(bool s)
{
	is_any_tag_valid = s;
}

bool TagManager::IsEveryParamValid() const
{
	return is_any_tag_valid;
}

void TagManager::SetParamWithoutTagsValid(bool s)
{
	is_none_tag_valid = s;
}

bool TagManager::IsParamWithoutTagsValid() const
{
	return is_none_tag_valid;
}

bool TagManager::IsPrefixValid(const std::wstring& prefix)
{
	if(IsEveryParamValid()) return true;

	for(const auto& tag : valid_tags)
	{
		if(prefix_tags[prefix].count(tag) > 0) {
			return true;
		}
	}

	if(IsParamWithoutTagsValid()) {
		if(prefix_tags[prefix].count(NONE_TAG) > 0) {
			return true;
		}
	}

	return false;
}

void TagManager::SetGlobalValidTags(const std::string& tags_string)
{
	if(tags_string == "") return;

	SetEveryParamValid(false);

	std::vector<std::string> tags;
	boost::split(tags, tags_string, [](char c){ return c == ',';});
	if(tags.size() > 0) SetParamWithoutTagsValid(false);

	SetFromTagVector(tags);

	if(valid_tags.empty() && IsParamWithoutTagsValid() == false && IsEveryParamValid() == false) {
		sz_log(1, "There are no valid tags in configuration - showing all tags");
		SetEveryParamValid(true);
		SetParamWithoutTagsValid(true);
	}
}

void TagManager::ResetGlobalTags(const std::vector<std::string>& tags)
{
	SetEveryParamValid(false);
	SetParamWithoutTagsValid(false);
	valid_tags.clear();
	SetFromTagVector(tags);
}

void TagManager::AddToAllTags(const std::string& tag)
{
	all_tags.insert(tag);
}

std::set<std::string> TagManager::GetAllTags()
{
	return all_tags;
}

void TagManager::SetFromTagVector(std::vector<std::string> tags)
{
	for(auto& tag : tags)
	{
		boost::trim(tag);
		if(tag == NONE_TAG) {
			SetParamWithoutTagsValid(true);
			continue;
		}

		if(tag == ALL_TAG) {
			SetEveryParamValid(true);
			continue;
		}

		if(ValidateTag(tag) == false) {
			sz_log(1, "Ignoring invalid command line tag %s", tag.c_str());
			continue;
		}

		valid_tags.insert(tag);
		AddToAllTags(tag);
	}
}

bool TagManager::ValidateTag(const std::string& tag) const
{
	const std::set<std::string> forbidden_tags = {
		NONE_TAG, ALL_TAG, ""
	};
	if(forbidden_tags.count(tag) > 0) return false;

	const std::set<char> forbidden_chars = {',', '+', '-', ' '};
	for(const auto& c : forbidden_chars)
	{
		if(tag.find(c) != std::string::npos)
			return false;
	}
	return true;
}

void TAttribHolder::parseTags()
{
	auto tags_string = getAttribute<std::string>("tags", "");
	TTagHolder::ParseTags(tags_string);
}

int TAttribHolder::parseXML(xmlNodePtr node) {
	for (xmlAttrPtr at = node->properties; at; at = at->next) {
		xmlChar* value = xmlNodeListGetString(node->doc, at->children, 1);
		xmlNs* ns = at->ns;
		if (ns) {
			if (SC::U2A(ns->href) == TAttribHolder::extra_ns_href) {
				storeAttribute(std::string("extra:")+SC::U2L(at->name), SC::U2L(value));
			} else {
				storeAttribute(SC::U2L(ns->prefix)+std::string(":")+SC::U2L(at->name), SC::U2L(value));
			}
		} else {
			storeAttribute(SC::U2L(at->name), SC::U2L(value));
		}

		xmlFree(value);
	}

	parseTags();
	return processAttributes();
}

int TAttribHolder::parseXML(xmlTextReaderPtr reader) {
	return TAttribHolder::parseXML(xmlTextReaderCurrentNode(reader));
}

int TAttribHolder::parseXML(const boost::property_tree::ptree& tree) {
	auto attrs = tree.get_child("<xmlattr>");
	for (const auto& attr: attrs) {
		auto attr_name = attr.first;
		size_t sep_pos = attr_name.find_first_of(":");
		if (sep_pos != std::string::npos) {
			auto ns_prefix = attr_name.substr(0, sep_pos);
			const std::set<std::string> ns_prefixes{"modbus", "iec", "k601", "exec", "mbus"};
			if(ns_prefixes.count(ns_prefix) > 0) {
				attr_name = "extra"+attr_name.substr(sep_pos);
			}
		}

		storeAttribute(attr_name, attr.second.data());
	}

	parseTags();
	return processAttributes();
}

template <>
std::string TAttribHolder::getAttribute(std::string attr_name) const {
	auto attr = attrs.find(attr_name);
	if (attr != attrs.end()) {
		return attr->second;
	}

	throw std::runtime_error("Attribute "+attr_name+" not present! Cannot continue!");
}


template <>
const std::string TAttribHolder::getAttribute(std::string attr_name) const {
	return getAttribute<std::string>(attr_name);
}


template <>
char* TAttribHolder::getAttribute(std::string attr_name) const {
	return strdup(getAttribute<std::string>(attr_name).c_str());
}


template <>
const char* TAttribHolder::getAttribute(std::string attr_name) const {
	return getAttribute<std::string>(attr_name).c_str();
}
